package com.basin.common.redis.aspect;

import com.basin.common.core.exception.BasinExCode;
import com.basin.common.core.exception.BasinException;
import com.basin.common.core.util.SpringElUtil;
import com.basin.common.redis.annotation.BasinLock;
import com.basin.common.redis.support.LockType;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;

import java.lang.reflect.Method;

/**
 * @author: Sam ZHONG | sammy1997@aliyun.com
 */
@Slf4j
@Aspect
@ConditionalOnBean(RedissonClient.class)
public class RLockAspect {

    @Resource
    private RedissonClient redissonClient;

    @Around("@annotation(com.basin.common.redis.annotation.BasinLock)")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        BasinLock lock = method.getAnnotation(BasinLock.class);
        String key = lock.key();
        Object[] args = pjp.getArgs();
        // 支持spring El表达式
        key = SpringElUtil.parse(key, method, args);
        // 获取锁
        RLock rLock = this.getLock(key, lock.type());
        boolean isLock = rLock.tryLock(lock.waitTime(), lock.limitTime(), lock.unit());
        if (!isLock) { // redis获取锁超时
            throw new BasinException(BasinExCode.REDIS_LOCK_ACQUIRE_TIMEOUT);
        }
        try {
            return pjp.proceed();
        } finally {
            // 释放锁
            rLock.unlock();
        }
    }

    /**
     * 根据类型和key获取锁
     *
     * @param key      key
     * @param lockType type
     * @return lock
     */
    private RLock getLock(@Nonnull String key, LockType lockType) {
        RLock lock;
        switch (lockType) {
            case REENTRANT_LOCK -> lock = this.redissonClient.getLock(key);
            case FAIR_LOCK -> lock = this.redissonClient.getFairLock(key);
            case READ_LOCK -> lock = this.redissonClient.getReadWriteLock(key).readLock();
            case WRITE_LOCK -> lock = this.redissonClient.getReadWriteLock(key).writeLock();
            default -> throw new IllegalArgumentException("Unsupported lock type: " + lockType);
        }
        return lock;
    }
}
